//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.Workflow.ComponentModel;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using System.Data.SqlClient;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;
    using System.Xml;
    using System.Workflow.Activities;
    using System.IO;
    using System.Security;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using System.Workflow.ComponentModel.Compiler;
    using System.Resources;
    using System.Collections.ObjectModel;
    using System.Globalization;

    #endregion Using Directives

    /// <summary>
    /// Holds the connector between 2 activities.
    /// </summary>
    public class WorkflowActivityConnectionElement : Canvas, IUpdatePointPositions
    {
        #region Private Variables

        /// <summary>
        /// Key for the dictionary object to retrieve the left connector.
        /// </summary>
        private const string LEFTCONNECTORKEY = "Left";

        /// <summary>
        /// Key for the dictionary object to retrieve the right connector.
        /// </summary>
        private const string RIGHTCONNECTORKEY = "Right";

        /// <summary>
        /// The UIElement to the left.
        /// </summary>
        private ITridentUIElement leftElement;

        /// <summary>
        /// The UIElement to the right.
        /// </summary>
        private ITridentUIElement rightElement;

        /// <summary>
        /// The connecting line.
        /// </summary>
        private Polyline line;

        /// <summary>
        /// The drop point, represented as an ellipse.
        /// </summary>
        private DropCircle dropCircle;

        /// <summary>
        /// The arrow head control for the connecting arrow.
        /// </summary>
        private Arrowhead arrowPath;

        /// <summary>
        /// The parent activity for this connector.
        /// </summary>
        private BaseCompositeModel parentModel;

        /// <summary>
        /// Workflow Composer.
        /// </summary>
        private WorkflowComposer wfLoader;

        /// <summary>
        /// The composer control containing this control.
        /// </summary>
        private ComposerControl composerControl;

        /// <summary>
        /// Factory for getting the activity models.
        /// </summary>
        private ModelFactory modelFactory;

        #endregion Private Variables

        #region Events

        /// <summary>
        /// Fired when the user chooses to create a subsection.
        /// </summary>
        public event EventHandler<SubsectionCreationEventArgs> CreateSubsection;

        #endregion Events

        #region Constructor

        /// <summary>
        /// This is the overloaded Constructor.
        /// This is used to connect a WorkflowActivityElement and WorkflowActivityElement.
        /// </summary>
        /// <param name="firstActivityElement">First Activity Element of type WorkflowActivityElement.</param>
        /// <param name="secondActivityElement">Second Activity Element of type WorkflowActivityElement.</param>
        /// <param name="wfLoader">Holds the workflow loader.</param>
        internal WorkflowActivityConnectionElement(
            ITridentUIElement leftElement,
            ITridentUIElement rightElement,
            BaseCompositeModel parentModel,
            WorkflowComposer wfLoader)
        {
            this.parentModel = parentModel;
            this.leftElement = leftElement;
            this.rightElement = rightElement;
            this.wfLoader = wfLoader;

            if (wfLoader != null)
            {
                this.modelFactory = new ModelFactory(wfLoader.ActComposer.ActivityMetadataList, wfLoader.ActComposer.SupportedActivityCollection);
            }

            InitComponent();
            if (parentModel.IsIfElseActivityModel)
            {
                this.dropCircle.Visibility = Visibility.Collapsed;
            }

            // Id needed for automation.
            this.dropCircle.Name = "DropCircle_" + ComposerRenderer.DropCircleCounter.ToString(CultureInfo.InvariantCulture);
            this.Loaded += new RoutedEventHandler(this.OnWorkflowActivityConnectionElementLoaded);
            this.Unloaded += new RoutedEventHandler(this.OnWorkflowActivityConnectionElementUnLoaded);
        }

        #endregion Constructor

        #region Public Properties

        /// <summary>
        /// Gets the left panel.
        /// </summary>
        public ITridentUIElement LeftElement
        {
            get
            {
                return this.leftElement;
            }
        }

        /// <summary>
        /// Gets the right panel.
        /// </summary>
        public ITridentUIElement RightElement
        {
            get
            {
                return this.rightElement;
            }
        }

        /// <summary>
        /// Gets or sets the index of the element to the left of this connector.
        /// The index is the location of the left element in it's parent children collection.
        /// </summary>
        private int IndexOfLeftElement
        {
            get
            {
                int index = -1;
                if (this.leftElement != null)
                {
                    index = this.leftElement.FetchUIElementPosition();
                }

                return index;
            }
        }

        #endregion Public Properties

        #region Methods

        /// <summary>
        /// This is used to update the positions of the connection element
        /// created with respect to the FrameworkElements in teh Activity Elements.
        /// </summary>
        public void UpdatePointPositions()
        {
            FrameworkElement first = this.LeftElement.RightControlFlowConnectionElement;
            FrameworkElement second = this.RightElement.LeftControlFlowConnectionElement;

            this.line.Stroke = ((Shape)first).Stroke;
            this.dropCircle.SetCircleColor(this.line.Stroke);

            // This will be the start point for the Connectivity Element.
            Point startPoint = first.TranslatePoint(new Point(first.Width, first.Height / 2.0), this);

            // This will be the End point for the Connectivity Element.
            Point endPoint = second.TranslatePoint(new Point(0, second.Height / 2.0), this);

            double width = endPoint.X - startPoint.X;
            double height = endPoint.Y - startPoint.Y;
            double midX = startPoint.X + (width / 2.0);
            double midY = startPoint.Y + (height / 2.0);

            // Mid Point of the Connectivity Element.
            Point midTopPoint = new Point(midX, startPoint.Y);
            Point midBotPoint = new Point(midX, endPoint.Y);

            line.Points.Clear();
            line.Points.Add(startPoint);
            line.Points.Add(midTopPoint);
            line.Points.Add(midBotPoint);
            line.Points.Add(endPoint);

            // This places the Circle in middle of the Connectivity Element.
            Canvas.SetLeft(dropCircle, midX - (dropCircle.Width / 2.0));
            Canvas.SetTop(dropCircle, midY - (dropCircle.Height / 2.0));
            Canvas.SetLeft(arrowPath, (endPoint.X - 12));
            Canvas.SetTop(arrowPath, endPoint.Y - 5.25);
        }

        /// <summary>
        /// The event handler for the loaded event.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event args.</param>
        private void OnWorkflowActivityConnectionElementLoaded(object sender, RoutedEventArgs e)
        {
            this.GetComposerControl();
            this.AddInteractionHandlers();
            this.UpdatePointPositions();
        }

        /// <summary>
        /// The event handler for the Unloaded event.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event args.</param>
        private void OnWorkflowActivityConnectionElementUnLoaded(object sender, RoutedEventArgs e)
        {
            this.RemoveInteractionHandlers();
        }

        /// <summary>
        /// This is used to initialize the Connection Element.
        /// </summary>
        /// <param name="flowFillBrush">Brush Used for the Connectivity Element.</param>
        private void InitComponent()
        {
            this.line = new Polyline();
            this.dropCircle = new DropCircle();
            this.arrowPath = new Arrowhead();

            ScaleTransform scaleTrans = new ScaleTransform(0.7, 0.7);
            this.arrowPath.RenderTransform = scaleTrans;
            this.line.StrokeThickness = 2;

            this.Children.Add(this.line);
            this.Children.Add(this.dropCircle);
            this.Children.Add(this.arrowPath);
        }

        /// <summary>
        /// Attach event handlers according to the mode.
        /// </summary>
        private void AddInteractionHandlers()
        {
            if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.dropCircle.DragOver += new DragEventHandler(this.OnDropAreaDragOver);
                this.dropCircle.Drop += new DragEventHandler(this.OnDrop);
                this.dropCircle.AllowDrop = true;
                this.CircleContextMenuGenerator();
            }
        }

        /// <summary>
        /// Remove event handlers according to the mode.
        /// </summary>
        private void RemoveInteractionHandlers()
        {
            if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.dropCircle.DragOver -= new DragEventHandler(this.OnDropAreaDragOver);
                this.dropCircle.Drop -= new DragEventHandler(this.OnDrop);
                this.dropCircle.ContextMenu = null;
            }
        }

        /// <summary>
        /// Create the context menu for the drop area.
        /// </summary>
        private void CircleContextMenuGenerator()
        {
            this.dropCircle.ContextMenu = new ContextMenu();

            if (SubsectionMetadata.Data != null)
            {
                MenuItem subsectionCreator = new MenuItem();
                subsectionCreator.Header = TridentResourceManager.GetString("CreateSubsection");
                subsectionCreator.Icon = GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/EmptySubSection.png");
                subsectionCreator.Click += new RoutedEventHandler(this.OnCreateNewSubsection);
                this.dropCircle.ContextMenu.Items.Add(subsectionCreator);
            }

            MenuItem pasteMenu = new MenuItem();
            pasteMenu.Header = TridentResourceManager.GetString("PasteMenuLabel");
            pasteMenu.Icon = GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ActivityPaste.png");
            pasteMenu.IsEnabled = false;
            pasteMenu.Click += new RoutedEventHandler(this.OnpasteMenuClick);
            this.dropCircle.ContextMenu.Items.Add(pasteMenu);

            this.dropCircle.ContextMenu.Opened += new RoutedEventHandler(this.OnContextMenuOpened);

        }

        /// <summary>
        /// Creates the Image object
        /// </summary>
        /// <param name="imagepath">string path of the image</param>
        /// <returns>Image</returns>
        protected static Image GetIcon(string imagePath)
        {
            Image headerImage = new Image();
            ImageSourceConverter converter = new ImageSourceConverter();
            headerImage.Source = converter.ConvertFromString(imagePath) as ImageSource;
            return headerImage;
        }

        /// <summary>
        /// Event hanler for opening the context menu
        /// contains the programing logic for enable/disable paste Menu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnContextMenuOpened(object sender, RoutedEventArgs e)
        {
            if (this.composerControl.CurrentSelectedElements.Count > 0)
            {
                this.composerControl.CurrentSelectedElements.RemoveAllItems();
            }

            if (this.dropCircle.ContextMenu.Items.Count > 1)
            {
                MenuItem pasteMenu = this.dropCircle.ContextMenu.Items[1] as MenuItem;
                if (pasteMenu != null)
                {
                    pasteMenu.IsEnabled = TridentClipboardService.HasData;
                }
            }
        }

        /// <summary>
        /// Fired on Create Subsections click.
        /// </summary>
        /// <param name="sender">
        /// Drop area.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnpasteMenuClick(object sender, RoutedEventArgs e)
        {
            if (TridentClipboardService.HasData)
            {
                SimpleActivityModel activity = (TridentClipboardService.Data as SimpleActivityModel).CopyActivityModel();
                if (activity != null)
                {
                    activity.UniqueId = activity.Name + "_" + ComposerRenderer.UniqueCounter.ToString(CultureInfo.InvariantCulture);
                    foreach (ParameterDescriptionModel param in activity.InputParameters)
                    {
                        if (param.IsDatabound)
                        {
                            param.IsDatabound = false;
                            param.Value = null;
                        }
                    }

                    // Insert the pasted activity
                    this.InsertNewElement(activity);
                    this.composerControl.IsWFCommitted = false;
                }
            }
        }

        /// <summary>
        /// Fired on Create Subsections click.
        /// </summary>
        /// <param name="sender">
        /// Drop area.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnCreateNewSubsection(object sender, RoutedEventArgs e)
        {
            try
            {
                if (this.CreateSubsection != null)
                {
                    SubsectionCreationEventArgs args = this.GenerateSubsectionCreationEventArgs(null);
                    this.CreateSubsection.Invoke(this, args);
                }
            }
            catch (TridentCustomException ex)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(ex.Message);
            }
        }

        /// <summary>
        /// Generate SubsectionCreationArgs.
        /// </summary>
        /// <param name="wfMeta">
        /// The workflow metadata.
        /// </param>
        /// <returns>
        /// SubsectionCreationEventArgs.
        /// </returns>
        private SubsectionCreationEventArgs GenerateSubsectionCreationEventArgs(WorkflowMetadata wfMeta)
        {
            return new SubsectionCreationEventArgs(this.parentModel, wfMeta, this.IndexOfLeftElement + 1);
        }

        /// <summary>
        /// Add a new activity to the current workflow.
        /// </summary>
        /// <param name="actModel">
        /// The activity model of the workflow.
        /// </param>
        private void InsertNewElement(BaseModel model)
        {
            if (this.parentModel != null && model != null)
            {
                this.parentModel.Children.Insert(this.IndexOfLeftElement + 1, model);
                model.ParentModel = this.parentModel;
                if (this.composerControl.UIElements.ContainsKey(this.parentModel.UniqueId))
                {
                    ICompositeUIElement parentElem = this.composerControl.UIElements[this.parentModel.UniqueId] as ICompositeUIElement;
                    if (parentElem != null)
                    {
                        TridentUIGenerationVisitor uiGenerator = new TridentUIGenerationVisitor();
                        ITridentUIElement childUiElem = uiGenerator.GenerateUIRepresentation(model, this.composerControl.DataTypes, this.composerControl.UIElements, this.composerControl.CurrentComposerRenderHelper);
                        if (childUiElem != null)
                        {
                            parentElem.InsertChildElement(childUiElem, this.IndexOfLeftElement + 1);
                            this.composerControl.CurrentComposerRenderHelper.connectionVisitor.InsertActivityConnection();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Adds the workflow to the current workflow, in the form of a subsection.
        /// </summary>
        /// <param name="wfMeta">
        /// The metadata of the new workflow.
        /// </param>
        private void AddNewWorkflowAsSubsection(WorkflowMetadata wfMeta)
        {
            if (this.CreateSubsection != null)
            {
                SubsectionCreationEventArgs args = this.GenerateSubsectionCreationEventArgs(wfMeta);
                this.CreateSubsection.Invoke(this, args);
            }
        }

        /// <summary>
        /// This is the event Handler for Drag Drop Event.
        /// This contains logic for adding new activities to th UI and 
        /// save the activity info to temp XOML.
        /// </summary>
        /// <param name="sender">Event Raiser.</param>
        /// <param name="e">Drag event Arguments.</param>
        private void OnDrop(object sender, DragEventArgs e)
        {
            if (this.modelFactory != null)
            {
                BaseModel model = null;
                try
                {
                    if (this.composerControl.CurrentSelectedElements.Count > 0)
                    {
                        this.composerControl.CurrentSelectedElements.RemoveAllItems();
                    }

                    ActivityMetadata metadata = (ActivityMetadata)e.Data.GetData(typeof(ActivityMetadata));

                    // Create a activity model out of the activity meta.
                    if (metadata != null)
                    {
                        model = this.modelFactory.FetchModelByActivityMetadata(metadata, ComposerRenderer.UniqueCounter.ToString(CultureInfo.InvariantCulture), this.parentModel);
                        this.InsertNewElement(model);
                        this.composerControl.IsWFCommitted = false;
                    }
                    else
                    {
                        WorkflowMetadata workflowMeta = (WorkflowMetadata)e.Data.GetData(typeof(WorkflowMetadata));
                        
                        if (workflowMeta != null && this.composerControl.CurrentWorkflowModel != null)
                        {
                            if (this.wfLoader.CheckIfWorkflowDeleted(workflowMeta.Id))
                            {
                                // If the workflow has been deleted.
                                TridentMessageBox.ShowTridentErrorMessageBox(TridentResourceManager.GetString("DeletedWorkflowDrag"));
                                return;
                            }
                            
                            this.AddNewWorkflowAsSubsection(workflowMeta);
                            this.composerControl.IsWFCommitted = false;
                        }
                    }

                    this.composerControl.RefreshUI();

                }
                catch (TridentCustomException ex)
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(
                        Application.Current.MainWindow,
                        ex.Message);

                    if (model != null)
                    {
                        // Get the parent and remove the model.
                        BaseCompositeModel parent = model.ParentModel;
                        if (parent != null && parent.Children.Contains(model) && parent.Children.Remove(model))
                        {
                            this.composerControl.RefreshUI();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// This is the event Handler for Drag Over Event.
        /// </summary>
        /// <param name="sender">Event Raiser.</param>
        /// <param name="e">Drag event Arguments.</param>
        private void OnDropAreaDragOver(object sender, DragEventArgs e)
        {
            bool validDrop = false;
            ActivityMetadata metadata = (ActivityMetadata)e.Data.GetData(typeof(ActivityMetadata));
            if (metadata != null)
            {
                validDrop = true;
            }
            else
            {
                WorkflowMetadata workflowMeta = (WorkflowMetadata)e.Data.GetData(typeof(WorkflowMetadata));
                if (workflowMeta != null && this.wfLoader != null && this.composerControl.CurrentWorkflowModel != null)
                {
                    validDrop = true;
                }
            }

            if (validDrop)
            {
                e.Effects = DragDropEffects.All;
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
        }

        /// <summary>
        /// Get the composer control instance.
        /// </summary>
        private void GetComposerControl()
        {
            string composerCtrlName = (string)this.GetValue(ComposerControl.ComposerControlNameProperty);
            this.composerControl = Application.Current.MainWindow.FindName(composerCtrlName) as ComposerControl;
        }

        #endregion Methods
    }
}
